Skip to main content
CORS (Cross-Origin Resource Sharing) issues occur when the frontend (running on one origin) tries to make requests to the backend API (running on a different origin). This guide helps you understand and resolve CORS errors.

Understanding CORS

In development, the frontend runs on http://localhost:5173 (Vite) and the backend on http://localhost:8080 (Spring Boot). These are different origins, so CORS must be configured.

Current CORS Configuration

The application’s CORS settings are configured in SecurityConfig.java:55-68:
@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOrigins(Arrays.asList(
        "http://localhost:5173", 
        "http://127.0.0.1:5173"
    ));
    configuration.setAllowedMethods(Arrays.asList(
        "GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"
    ));
    configuration.setAllowedHeaders(Arrays.asList(
        "Authorization", "Content-Type", "X-Requested-With", 
        "Accept", "Origin", "Access-Control-Request-Method", 
        "Access-Control-Request-Headers"
    ));
    configuration.setExposedHeaders(Arrays.asList("Authorization"));
    configuration.setAllowCredentials(true);
    configuration.setMaxAge(3600L);

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
}

Common CORS Errors

Error in Browser Console:
Access to XMLHttpRequest at 'http://localhost:8080/api/productos' 
from origin 'http://localhost:5173' has been blocked by CORS policy: 
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Causes:
  1. Frontend origin not in allowed origins list
  2. CORS configuration not applied to all endpoints
  3. Backend not running or crashed
Solutions:
  1. Verify frontend URL matches allowed origins:
    • Check the URL in your browser address bar
    • Must be exactly http://localhost:5173 or http://127.0.0.1:5173
  2. Add your origin to SecurityConfig.java:
configuration.setAllowedOrigins(Arrays.asList(
    "http://localhost:5173",
    "http://127.0.0.1:5173",
    "http://localhost:3000" // Add if using different port
));
  1. For development, allow all origins (NOT for production):
configuration.setAllowedOriginPatterns(Arrays.asList("*"));
// Instead of setAllowedOrigins
  1. Verify CORS is applied globally:
    • Check that corsConfigurationSource() bean is defined
    • Ensure it’s registered in the security filter chain
  2. Restart the backend after changes:
cd Iqüea_back
mvn spring-boot:run
Never use setAllowedOriginPatterns("*") with setAllowCredentials(true) in production. Always specify exact origins.
Error Message:
Access to XMLHttpRequest at 'http://localhost:8080/api/pedidos' 
from origin 'http://localhost:5173' has been blocked by CORS policy: 
Request header field authorization is not allowed by 
Access-Control-Allow-Headers in preflight response.
Cause: The Authorization header is not in the allowed headers list.Solution:
  1. Verify allowed headers include Authorization:
    • Check SecurityConfig.java:59-61
    • Should include: "Authorization"
  2. If missing, add it:
configuration.setAllowedHeaders(Arrays.asList(
    "Authorization",  // Must be included
    "Content-Type", 
    "X-Requested-With", 
    "Accept",
    "Origin"
));
  1. Or allow all headers (development only):
configuration.setAllowedHeaders(Arrays.asList("*"));
  1. Ensure frontend sends correct header format:
fetch('http://localhost:8080/api/pedidos', {
  headers: {
    'Authorization': `Bearer ${token}`, // Correct format
    'Content-Type': 'application/json'
  }
})
Error Message:
Access to XMLHttpRequest at 'http://localhost:8080/api/productos/1' 
from origin 'http://localhost:5173' has been blocked by CORS policy: 
Method DELETE is not allowed by Access-Control-Allow-Methods.
Cause: The HTTP method is not in the allowed methods list.Solution:
  1. Check allowed methods in SecurityConfig.java:58:
configuration.setAllowedMethods(Arrays.asList(
    "GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"
));
  1. Verify the method is included - all common methods should be there
  2. If needed, add missing method:
configuration.setAllowedMethods(Arrays.asList(
    "GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH", "HEAD"
));
  1. Always include OPTIONS for preflight requests
Error Message:
Access to XMLHttpRequest at 'http://localhost:8080/api/productos' 
from origin 'http://localhost:5173' has been blocked by CORS policy: 
Response to preflight request doesn't pass access control check.
What is a preflight request?Browsers send an OPTIONS request before certain requests (those with custom headers or methods other than GET/POST) to check if the actual request is allowed.Solutions:
  1. Ensure OPTIONS method is allowed:
configuration.setAllowedMethods(Arrays.asList(
    "GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"
));
  1. Set maxAge to cache preflight responses:
configuration.setMaxAge(3600L); // Cache for 1 hour
  • Already configured in line 63
  1. Check that CSRF is disabled (required for CORS):
    • In SecurityConfig.java:27:
http.csrf(csrf -> csrf.disable())
  1. Verify OPTIONS requests aren’t blocked by security:
    • OPTIONS requests should not require authentication
  2. Check browser DevTools Network tab:
    • Look for OPTIONS request
    • Check response headers:
      • Access-Control-Allow-Origin
      • Access-Control-Allow-Methods
      • Access-Control-Allow-Headers
Error Message:
Access to XMLHttpRequest at 'http://localhost:8080/api/auth/login' 
from origin 'http://localhost:5173' has been blocked by CORS policy: 
The value of the 'Access-Control-Allow-Credentials' header in the response 
is '' which must be 'true' when the request's credentials mode is 'include'.
Cause: Mismatch between frontend credentials setting and backend CORS configuration.Solutions:
  1. Backend: Ensure credentials are allowed:
    • Check SecurityConfig.java:62:
configuration.setAllowCredentials(true);
  1. Frontend: Send credentials if needed:
fetch('http://localhost:8080/api/auth/login', {
  method: 'POST',
  credentials: 'include', // Sends cookies
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ email, password })
})
  1. If not using cookies, omit credentials:
// If using JWT in localStorage, credentials not needed
fetch('http://localhost:8080/api/productos', {
  headers: {
    'Authorization': `Bearer ${localStorage.getItem('token')}`
  }
  // No credentials: 'include' needed
})
Iquea Commerce uses JWT tokens stored in localStorage, so credentials: 'include' is typically not needed unless you’re also using cookies.

Development vs Production

Development Configuration:Allow localhost origins:
configuration.setAllowedOrigins(Arrays.asList(
    "http://localhost:5173",
    "http://127.0.0.1:5173",
    "http://localhost:3000"
));
Production Configuration:
  1. Use environment variables:
@Value("${app.cors.allowed-origins}")
private String allowedOrigins;

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOrigins(
        Arrays.asList(allowedOrigins.split(","))
    );
    // ... rest of configuration
}
  1. In application.properties:
# Development
app.cors.allowed-origins=http://localhost:5173,http://127.0.0.1:5173

# Production (application-prod.properties)
app.cors.allowed-origins=https://iquea.com,https://www.iquea.com
  1. Security best practices:
    • Always use HTTPS in production
    • Specify exact domains (no wildcards)
    • Keep credentials enabled only if needed
    • Set appropriate maxAge for caching
Example Production Configuration:
if (environment.equals("production")) {
    configuration.setAllowedOrigins(Arrays.asList(
        "https://iquea.com",
        "https://www.iquea.com"
    ));
} else {
    configuration.setAllowedOriginPatterns(Arrays.asList("*"));
}
If using a reverse proxy in production:Nginx:
location /api {
    proxy_pass http://localhost:8080;
    
    # Let Spring Boot handle CORS
    proxy_set_header Origin $http_origin;
    proxy_set_header Host $host;
    
    # Or handle CORS in Nginx
    add_header Access-Control-Allow-Origin $http_origin always;
    add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
    add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
    add_header Access-Control-Allow-Credentials true always;
    
    if ($request_method = OPTIONS) {
        return 204;
    }
}
Apache:
<Location /api>
    ProxyPass http://localhost:8080/api
    ProxyPassReverse http://localhost:8080/api
    
    Header set Access-Control-Allow-Origin "%{HTTP_ORIGIN}e"
    Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
    Header set Access-Control-Allow-Headers "Authorization, Content-Type"
    Header set Access-Control-Allow-Credentials "true"
</Location>
If configuring CORS in your proxy, disable or adjust the Spring Boot CORS configuration to avoid conflicts.

Debugging CORS Issues

Browser DevTools

  1. Open DevTools (F12) → Network tab
  2. Reproduce the error
  3. Look for the failed request
  4. Check Request Headers:
    • Origin: http://localhost:5173
    • Access-Control-Request-Method: POST (for preflight)
    • Access-Control-Request-Headers: authorization (for preflight)
  5. Check Response Headers:
    • Access-Control-Allow-Origin: http://localhost:5173
    • Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS, PATCH
    • Access-Control-Allow-Headers: Authorization, Content-Type
    • Access-Control-Allow-Credentials: true

Testing with cURL

# Test preflight request
curl -X OPTIONS http://localhost:8080/api/productos \
  -H "Origin: http://localhost:5173" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Authorization, Content-Type" \
  -v

# Test actual request
curl -X GET http://localhost:8080/api/productos \
  -H "Origin: http://localhost:5173" \
  -v
Look for Access-Control-* headers in the response.

Common Mistakes

Common CORS configuration mistakes:
  1. Using http://localhost:5173/ (with trailing slash) - should be http://localhost:5173
  2. Forgetting to restart backend after configuration changes
  3. Using wildcards with credentials in production
  4. Not including OPTIONS in allowed methods
  5. Blocking OPTIONS requests with authentication
  6. Typos in header names (e.g., Authorisation instead of Authorization)
  7. Testing with browser extensions that modify headers
  8. Configuring CORS in multiple places (proxy + Spring Boot)

Quick Checklist

When troubleshooting CORS issues:
  • Backend is running and accessible
  • Frontend origin is in allowedOrigins list
  • All required HTTP methods are in allowedMethods
  • OPTIONS method is included for preflight
  • Required headers are in allowedHeaders
  • allowCredentials matches frontend usage
  • Backend was restarted after configuration changes
  • Browser cache is cleared (Ctrl+Shift+Delete)
  • No browser extensions interfering with requests
  • CSRF is disabled in Spring Security

Additional Resources